# -*- coding: utf-8 -*-
import numpy
import struct
from pulser import Pulser

from pi3diamond import pi3d

class PulseGenerator():
	def __init__(self, serial='', channel_map = {'ch0':0,'ch1':1,'ch2':2,'ch3':3} ):
		self.channel_map = channel_map
		pulser=Pulser(serial=serial)
		pulser.setResetValue(0,0,0,0)
		pulser.disableDecoder()
		pulser.reset()
		self.pulser=pulser

	def Sequence(self,seq,loop=None):
		print self.pulser.loadPages(self.convertSequenceToBinary(seq)), "Bytes written."
		self.pulser.run()

	def Run(self):
		self.pulser.run()

	def Halt(self):
		self.pulser.halt()

	def Night(self):
		self.pulser.setResetValue(False,False,False,False)
		self.Halt()

	def Light(self):
		self.pulser.setResetValue(True,False,False,False)
		self.Halt()

#########PATTERN CALCULATION##########
	
	# time is specified in ns

	dt=1.5 # timing step length (1.6ns)

	def createBitsFromChannels(self,channels):
		bits = numpy.array((False, False, False, False))
		for channel in channels:
			bits[self.channel_map[channel]] = True
		return bits

	def setBits(self,integers,start,count,bits):
		"""Sets the bits in the range start:start+count in integers[i] to bits[i]."""
		# ToDo: check bit order (depending on whether least significant or most significant bit is shifted out first from serializer)
		for i in range(4):
			if bits[i]:
				integers[i] = integers[i] | (2**count-1) << start

	def pack(self,repetition, pattern):
		print repetition-1, pattern
		return struct.pack('IBBBB', repetition-1, pattern[2], pattern[3], pattern[0], pattern[1])

	def convertSequenceToBinary(self,sequence):
		"""Converts a pulse sequence into a series of pulser instructions,
		taking into account the 8bit minimal pattern length. The pulse sequence
		is described as a list of tuples (channels, time). The pulser instructions
		are of the form 'repetition (32 bit) | ch0 pattern (8bit), ..., ch3 pattern (8bit)'.
		If necessary, high level pulse commands are split into a series of
		suitable low level pulser instructions."""
		buf = ''
		blank = numpy.zeros((4,),dtype=int)
		pattern = blank.copy()
		index = 0
		for channels, time in sequence:
			ticks = int(round(time/self.dt))
			if ticks is 0:
				continue
			bits = self.createBitsFromChannels(channels)
			if index + ticks < 8: # if pattern does not fill current block, append to current block and continue
				self.setBits(pattern,index,ticks,bits)
				index += ticks
				continue
			if index > 0: # else fill current block with pattern, reduce ticks accordingly, write block and start a new block 
				self.setBits(pattern,index,8-index,bits)
				buf += self.pack(1,pattern)
				ticks -= ( 8 - index )
				pattern = blank.copy()
			repetitions = ticks / 8 # number of full blocks
			index = ticks % 8 # remainder will make the beginning of a new block
			if repetitions > 0:
				buf += self.pack(repetitions,255*bits)
			if index > 0:
				pattern = blank.copy()
				self.setBits(pattern,0,index,bits)
		if index > 0: # fill up incomplete block with zeros and write it
			self.setBits(pattern,index,8-index,numpy.array((False,False,False,False)))
			buf += self.pack(1,pattern)
		print "buf has",len(buf)," bytes"
		buf=buf+((1024-len(buf))%1024)*'\x00' # pad buffer with zeros so it matches SDRAM / FIFO page size
		print "buf has",len(buf)," bytes"
		return buf


########## TESTCODE############


if __name__ == '__main__':
	
	PG = PulseGenerator()
	
	PG.Sequence([(['laser'],3),([],6*1.5),(['mw'],127)] )
	
#	def high(self,x):
#		return([(['laser'],x) , ( ['mw'],x ) ] )
#	
#	def low(self,x):
#		return([ (['mw'],x), ( ['laser'],x ) ] )
